package com.wangjiang.foundation.mybatis.utils;

import com.wangjiang.foundation.common.util.StringUtil;
import com.wangjiang.foundation.mybatis.params.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.util.CollectionUtils;
import wiremock.com.google.common.collect.Lists;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

public class QueryParamsUtil {

    public static final String OR_SEPARATOR = "_or_";
    public static final String AND_SEPARATOR = "_and_";

    public static final String DEFAULT_PREFIX = "f_";

    public static final String JOIN_PREFIX = "j_";

    private static final String SORT_PREFIX = "sort_";
    /**
     * 升序
     */
    private static final String SORT_ASC = "asc";
    /**
     * 升序排
     */
    private static final String SORT_DESC = "desc";

    /**
     * 从HttpRequest中创建Term列表
     * Term命名规则为属性前缀_比较类型_属性名.
     * <p>
     * eg.
     * 简单查询
     * f_eq_name=a  sql : name=a
     * f_like_name=b sql : name like '%b%'
     * f_llike_name=b sql : name like '%b'
     * f_rlike_name=b sql : name linke 'b%'
     *
     * 多字段查询
     *
     * </p>
     * 命名规则为属性前缀j_比较类型_前缀为f的属性名
     * <p>
     *
     *     上述f前缀开头的数据为 f_eq_name
     *     需要和这个属性做or或者and 关联， 则为 j_eq_name_or_like_code
     *     如果没有f_eq_name的属性，那么这条数据就没有意义。
     *     注意，必须字段名和比较类型都相同
     *     eg.
     *     f_eq_name=a&j_eq_name_or_like_code=10
     *     sql 为  where ... and (name = a or code like '%10%')
     *
     *     eg. 只有字段名和比较类型都相同相同的才join起来。，如 f_like_name 就没有join
     *     f_eq_name=a&f_like_name=b&j_eq_name_or_like_code=10
     *     sql 为  where ... and (name = a or code like '%10%') and name like '%b%'
     *
     * </p>
     *
     */
    public static List<Term> buildFrom(HttpServletRequest request) {
        Map<String, Object> filterParamMap = ServletUtil.getParametersStartingWith(request, DEFAULT_PREFIX);
        List<Term> termList = buildFrom(filterParamMap, QueryParamsUtil::buildTerm);

        Map<String, Object> filterJoinParamMap = ServletUtil.getParametersStartingWith(request, JOIN_PREFIX);
        List<Term> joinTermList = buildFrom(filterJoinParamMap, QueryParamsUtil::buildJoinTerm);

        if (CollectionUtils.isEmpty(joinTermList)) {
            return termList;
        }

        final Map<String, List<Term>> termMap =
                termList.stream().collect(Collectors.groupingBy(term -> getTermString(term)));

        joinTermList.forEach(term -> {
            if (CollectionUtils.isEmpty(term.getTerms())) {
                termList.add(term);
                return ;
            }
            String termString = getTermString(term);
            List<Term> terms = termMap.get(termString);
            if (CollectionUtils.isEmpty(terms)) {
                return;
            }
            terms.forEach(t -> t.getTerms().addAll(term.getTerms()));
        });

        return termList;
    }

    public static void paddingTerms(QueryParams queryParams, HttpServletRequest request) {
        List<Term> terms = buildFrom(request);
        queryParams.addTerms(terms);
    }

    public static void paddingSortParam(QueryParams queryParams, HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        List<OrderParam> sorts = new ArrayList<>();
        for (String key : parameterMap.keySet()) {
            String value = parameterMap.get(key)[0];
            if (key.startsWith(SORT_PREFIX) && StringUtils.isNotEmpty(value)) {
                String substring = key.split("_")[1];
                OrderParam sort = new OrderParam();
                sort.setColumn(substring);
                switch (value) {
                    case SORT_ASC:
                        sort.setOrder(OrderEnum.ASC);
                        break;
                    case SORT_DESC:
                        sort.setOrder(OrderEnum.DESC);
                        break;
                    default:
                }
                sorts.add(sort);
                queryParams.setOrders(sorts);
            }
        }

        if (queryParams.getOrders().isEmpty()) {
            queryParams.setDefaultOrder();
        }
    }


    public static List<Term> buildFrom(Map<String, Object> filterParamMap, BiFunction<String, String, Term> buildTerm) {
        List<Term> terms = Lists.newArrayList();
        //分析参数Map,构造PropertyFilter列表
        for (Map.Entry<String, Object> entry : filterParamMap.entrySet()) {
            String filterName = StringUtils.substringBefore(entry.getKey(), "-");
            Object value = entry.getValue();
            //如果value值为空,则忽略此filter.
            if (value == null) {
                continue;
            }
            if (value.getClass().isArray()) {
                for (int i = 0; i < Array.getLength(value); i++) {
                    /*Term term = buildTerm(filterName, StringUtil.toString(Array.get(value, i)));*/
                    Term term = buildTerm.apply(filterName, StringUtil.toString(Array.get(value, i)));
                    terms.add(term);
                }
            } else {
                String strValue = StringUtil.toString(value);
                if (StringUtils.isBlank(strValue)) {
                    continue;
                }
                /*Term filter = buildTerm(filterName, strValue);*/
                Term filter = buildTerm.apply(filterName, strValue);
                terms.add(filter);
            }

        }
        return terms;
    }



    /**
     * @param name  比较属性字符串,含待比较的比较类型、属性值类型及属性列表.
     *              eg. like_name_or_login_name
     * @param value 待比较的值.
     */
    private static Term buildTerm(String name, String value) {
        String matchTypeCode = StringUtils.lowerCase(StringUtils.substringBefore(name, "_"));
        TermEnum matchType;
        try {
            matchType = Enum.valueOf(TermEnum.class, matchTypeCode);
        } catch (RuntimeException e) {
            throw new IllegalArgumentException("filter名称" + name + "没有按规则编写,无法得到属性比较类型.", e);
        }

        String propertyNameStr = StringUtils.substringAfter(name, "_");
        Validate.isTrue(StringUtils.isNotBlank(propertyNameStr), "filter名称" + name + "没有按规则编写,无法得到属性名称.");
        String[] propertyNames = StringUtils.splitByWholeSeparator(propertyNameStr, OR_SEPARATOR);
        Term term = null;
        for (String propertyName : propertyNames) {
            if (term == null) {
                term = Term.build(propertyName, matchType, value);
            } else {
                term.or(propertyName, matchType, value);
            }
        }
        return term;

    }


    /**
     * @param name  比较属性字符串,含待比较的比较类型、属性值类型及属性列表.
     *              and 或者 or 只能包含一个
     *              eg. like_name_or_eq_login_name
     *              eg. like_name_and_eq_login_name
     * @param value 待比较的值.
     */
    private static Term buildJoinTerm(String name, String value) {

        String separator = name.indexOf(OR_SEPARATOR) < 0 ? AND_SEPARATOR : OR_SEPARATOR;

        // 用于组合term，的type 为 and - 注意：最后是和这个名称的term进行组合
        String columnTarget = StringUtils.substringBefore(name, separator);
        Term termTarget = buildSingleTerm(Term.Type.and, columnTarget, value);

        // 被组合的term根据前缀来判断是 or ， and
        String columnNexus = StringUtils.substringAfter(name, separator);
        Term.Type termType = separator == OR_SEPARATOR ? Term.Type.or : Term.Type.and;
        Term termNexus = buildSingleTerm(termType, columnNexus, value);

        if (termTarget == null) {
            return termNexus;
        }

        if (termNexus == null) {
            return termTarget;
        }

        return termTarget.addTerm(termNexus);
    }


    /**
     * @param column eg. like_name
     *               eg. eq_login_name
     * @param value
     * @return
     */
    private static Term buildSingleTerm(Term.Type termType, String column, String value) {
        if (StringUtils.isBlank(column)) {
            return null;
        }

        Term term = buildTerm(column, value);
        term.setType(termType);
        return term;
    }

    private static String getTermString(Term term) {
        if (term.getTermType() == null) {
            return null;
        }
        return term.getTermType().name() + "_" + term.getColumn();
    }


}
